
package gameoflife;

/**
 * Welcome to the Simulator class for the one dimensional game of life.
 * 
 * This class directly interfaces with the UpdateRules and Generation objects
 * to produce a simulation.
 *
 *
 * @author CJ
 */
public class Simulator {

    //Instance Variables
    private Generation currentGeneration;
    private UpdateRules updateRules;

    //Constructors
    /**
     * Basic constructor for the Simulator object.  In order to change the
     * generation size or updateRules, one must instaniate a new Simulator
     * object.
     *
     * Constructor also fills the generation with random individuals.
     *
     * @param updateRules, Update rules for the simulation to be run
     * @param generationSize, Generation size for the simulation to be run
     */
    public Simulator(UpdateRules updateRules, int generationSize){
        this.currentGeneration = new Generation(generationSize);
        this.updateRules = updateRules;
        generateRandom();
    }


    //Public Methods
    /**
     * Simulates one generation with the updateRules and generation size this
     * object was constructed with.
     * 
     * @throws SimulationErrorException, if simulation was unsucessful
     */
    public void simulate() throws SimulationErrorException{
        applyUpdateRules();
    }


    /**
     * Basic method that allows others the String of the currentGeneration.
     *
     * @return String of current generation.
     */
    public String getGenerationString(){
        return this.currentGeneration.toString();
    }

    //Private Methods

    /**
     * Private method that applys the updateRules that was specificed when this
     * object was constructed.  This is the main driver where majoriy of the
     * logic takes place
     * 
     * @throws SimulationErrorException, if simulation was unsucessful
     */
    private void applyUpdateRules() throws SimulationErrorException{
        try{
            int previousValue = 0;
            int currentValue = 0;
            int nextValue = 0;

            /*
             * Main logic below.  Pretty much compute the current individual and
             * next.  Then get the resulting value into the generation.  This is
             * destructive of the current generation.  This allows it to
             * "overwrite" the previous generation as it computes the next one.
             * */
            for(int i = 0; i < this.currentGeneration.getGenerationSize(); ++i){

                currentValue = this.currentGeneration.getIndividual(i);
                nextValue = getNextIndividual(i);

                int sum = previousValue + currentValue + nextValue;
                int resultingValue = this.updateRules.getSumResult(sum);
                previousValue = currentValue;

                this.currentGeneration.setIndividual(i, resultingValue);
            }
        }catch(IllegalArgumentException e){
            throw new SimulationErrorException("IllegalArgument passed in applyUpdateRules.", e);
        }catch(ArrayIndexOutOfBoundsException e){
            e.printStackTrace();
            throw new SimulationErrorException("ArrayIndex out of bounds in applyUpdateRules.", e);
        }catch(Exception e){
            throw new SimulationErrorException(e.getMessage(), e);
        }
    }

    /**
     * Private helper method that determines if the next index for
     * the individual is out of bounds.
     * If it is, this method returns 0.  It is designed to assist in the
     * evalulation of the generation.
     *
     * @param index of current individual
     * @return the value, 0 if index+1 is out of bounds, else the retrived val
     */
    private int getNextIndividual(int index){
        if(index + 1 >= this.currentGeneration.getGenerationSize())
            return 0;
        else
            return this.currentGeneration.getIndividual(index + 1);
    }


    /**
     * Private method that fills the current generation with random individuals.
     * Either 0 or 1.
     */
    private void generateRandom(){
        for(int i = 0; i < this.currentGeneration.getGenerationSize(); ++i){
            //Random value either 0 or 1
            int value = (int)(Math.random() * 2);
            this.currentGeneration.setIndividual(i, value);
        }
    }

}
